package com.zy.debugger;

import com.sun.jdi.*;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.event.*;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.tools.jdi.SocketAttachingConnector;

import java.util.List;
import java.util.Map;

/**
 *  通过JNI工具测试Debug
 * @author zhangyu
 * @date 2022/2/20
*/
public class TestDebugVirtualMachine {


    private static VirtualMachine vm;

    public static void main(String[] args) throws Exception {
        //获取SocketAttachingConnector,连接其它JVM称之为附加(attach)操作
        VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
        List<AttachingConnector> connectors = vmm.attachingConnectors();
        SocketAttachingConnector sac = null;
        for(AttachingConnector ac : connectors) {
            if(ac instanceof SocketAttachingConnector) {
                sac = (SocketAttachingConnector) ac;
            }
        }
        assert sac != null;
        //设置好主机地址，端口信息
        Map<String, Connector.Argument> arguments = sac.defaultArguments();
        Connector.Argument hostArg = arguments.get("hostname");
        Connector.Argument portArg = arguments.get("port");
        hostArg.setValue("127.0.0.1");
        portArg.setValue(String.valueOf(8800));
        //进行连接
         vm = sac.attach(arguments);
        //相应的请求调用通过requestManager来完成
        EventRequestManager eventRequestManager = vm.eventRequestManager();
        //创建一个代码判断，因此需要获取相应的类，以及具体的断点位置，即相应的代码行。
        ClassType clazz = (ClassType) vm.classesByName("com.zy.debugger.DebuggerApplication").get(0);
        //设置断点代码位置
        Location location = clazz.locationsOfLine(22).get(0);
        //创建新断点并设置阻塞模式为线程阻塞，即只有当前线程被阻塞
        BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(location);
        //设置阻塞并启动
        breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
        breakpointRequest.enable();
        //获取vm的事件队列
        EventQueue eventQueue = vm.eventQueue();
        while(true) {
            //不断地读取事件并处理断点记录事件
            EventSet eventSet = eventQueue.remove();
            EventIterator eventIterator = eventSet.eventIterator();
            while(eventIterator.hasNext()) {
                Event event = eventIterator.next();
                execute(event);
            }
            //将相应线程resume，表示继续运行
            eventSet.resume();
        }
    }

    /**
     *  处理监听到事件
     * @author zhangyu
     * @date 2022/2/20
    */
    public static void execute(Event event) throws Exception {
        //获取的event为一个抽象的事件记录，可以通过类型判断转型为具体的事件，这里我们转型为BreakpointEvent，即断点记录，
        BreakpointEvent breakpointEvent = (BreakpointEvent) event;
        //并通过断点处的线程拿到线程帧，进而获取相应的变量信息，并打印记录。
        ThreadReference threadReference = breakpointEvent.thread();
        StackFrame stackFrame = threadReference.frame(0);
        List<LocalVariable> localVariables = stackFrame.visibleVariables();
        //输出当前线程栈帧保存的变量数据
        localVariables.forEach(t -> {
            Value value = stackFrame.getValue(t);
            System.out.println("local->" + value.type() + "," + value.getClass() + "," + value);
        });
    }

}